﻿using System;
using System.Collections.Generic;
using System.Text;
using ITAP.ShprossesBuilder.Calculations;
using ITAP.Core.Modelling.Primitives;
using System.Diagnostics;
using ITAP.Core.Modelling;
using ITAP.Core.Calculations;

namespace ITAP.ShprossesBuilder.Modelling.Components.Constraints
{
    [Serializable]
    public class FixSnapOnCapForCoveredComponentConstraint : IConstraint, IModelVisitor
    {
        private bool enabled = true;
        #region IConstraint Members

        public bool Check(IComponent component)
        {
            return false;
        }

        public void Fix(IComponent component)
        {
            if (!enabled)
                return;
            component.AcceptVisitor(this);
        }

        public bool Enabled
        {
            get
            {
                return enabled;
            }
            set
            {
                enabled = value;
            }
        }

        public ConstraintAction Action
        {
            get { return ConstraintAction.Fix; }
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            FixSnapOnCapForCoveredComponentConstraint clone = new FixSnapOnCapForCoveredComponentConstraint();
            clone.enabled = this.enabled;
            return clone;
        }

        #endregion

        #region IModelVisitor Members

        public void Visit(Model model)
        {
            throw new NotImplementedException();
        }

        public void Visit(Connector connector)
        {
            throw new NotImplementedException();
        }

        public void Visit(Shpross shpross)
        {
            throw new NotImplementedException();
        }

        public void Visit(Spacer spacer)
        {
            throw new NotImplementedException();
        }

        public void Visit(ShprossConnectorStub stub)
        {
            throw new NotImplementedException();
        }

        public void Visit(ShprossConnector shprossConnector)
        {
            SnapOnCap cap = shprossConnector.SnapOnCap;
            int pointsCount = 0;
            if (cap != null)
            {
                shprossConnector.FitSnapOnCap();
                List<ModelVector> vectors = new List<ModelVector>();
                List<ModelPoint> points = new List<ModelPoint>();
                if (cap.AutoMount)
                {
                    foreach (IComponent connector in shprossConnector.InnerComponents)
                        if (connector is Connector)
                        {
                            Connector _connector = ((Connector)connector);
                            ModelVector vector = new ModelVector(_connector.EndPoint.X - shprossConnector.Position.X, _connector.EndPoint.Y - shprossConnector.Position.Y).Normalize();
                            vector = vector.Multiply(cap.CapLength);
                            vectors.Add(vector);
                            points.Add(ModelCalculations.GetOffsetPoint(shprossConnector.Position, vector));
                        }
                    if (cap.EndsCount == points.Count)
                    {
                        cap.Recreate(shprossConnector.Position, points.ToArray());
                        pointsCount = points.Count;
                    }
                }
                else
                {
                    Angle shprossConnectorAngle = shprossConnector.Angle;
                    cap.Recreate(cap.InitialCenter, cap.InitialPoints.ToArray());
                    cap.Rotate(shprossConnectorAngle.Value, cap.Position);
                    cap.MoveTo(shprossConnector.Position);
                    pointsCount = cap.EndsCount;
                }

                if (pointsCount != shprossConnector.SnapOnCap.EndsCount)
                {
                    shprossConnector.RemoveInnerComponent(cap);
                    return;
                }                
            }
        }

        public void Visit(SpacerElement spacerElement)
        {
            throw new NotImplementedException();
        }

        public void Visit(SpacerConnector spacerConnector)
        {
            //SnapOnCap cap = spacerConnector.SnapOnCap;
            //int pointsCount = 0;
            //if (cap != null)
            //{

            //}
        }

        public void Visit(ModelCell modelCell)
        {
            throw new NotImplementedException();
        }

        public void Visit(ModelContainer modelContainer)
        {
            throw new NotImplementedException();
        }

        public void Visit(StickConnector stickConnector)
        {
            SnapOnCap cap = stickConnector.SnapOnCap;

            if (cap != null)
            {
                stickConnector.FitSnapOnCap();
                if (cap.EndsCount == 3)
                {
                    // TODO: Бага, иногда бывает null (начало отдисконнекчено от бока шпросса), поэтому сделал такую проверку...
                    if (stickConnector.StartConnectedComponent == null)
                        return;

                    IEndedPrimitive endedConnectedPrimitive = (IEndedPrimitive)stickConnector.StartConnectedComponent.Primitives[0];
                    IEndedPrimitive stickConnectorPrimitive = (IEndedPrimitive)stickConnector.Primitives[0];
                    ModelPoint[] points = stickConnector.Primitives[0].Intersect(stickConnector.StartConnectedComponent.Primitives[0], EndType.Start);

                    ModelPoint center = points[0];
                    ModelVector offsetVector = stickConnector.Primitives[0].GetTangent(0);
                    ModelPoint upPoint;
                    ModelPoint leftPoint;
                    ModelPoint rightPoint;

                    offsetVector = offsetVector.Multiply(cap[1]);
                    upPoint = ModelCalculations.GetOffsetPoint(center, offsetVector);
                    // StickConnector может коннектиться только с компонентами, 
                    // имеющими начало и конец (например, к одной из сторон шпросса).
                    double pathLength = endedConnectedPrimitive.GetPathLengthFromProjectionToStart(center);
                    if (double.IsNaN(pathLength) || double.IsInfinity(pathLength))
                    {
                        stickConnector.RemoveInnerComponent(cap);
                        return;
                    }
                    offsetVector = 
                        stickConnector.StartConnectedComponent.Primitives[0].GetTangent(pathLength / endedConnectedPrimitive.Length);

                    Angle angleBetweenConnectorAndComponent = ModelCalculations.GetAngleBetweenVectors(offsetVector, stickConnectorPrimitive.GetTangent(0), true);
                    // StickConnector приконнекчен к верхней границе компонента
                    if (angleBetweenConnectorAndComponent.Value < Math.PI)
                    {
                        offsetVector.Length = cap[0];
                        rightPoint = ModelCalculations.GetOffsetPoint(center, offsetVector);
                        offsetVector = offsetVector.Rotate(new Angle(Math.PI));
                        offsetVector.Length = cap[2];
                        leftPoint = ModelCalculations.GetOffsetPoint(center, offsetVector);
                        cap.Recreate(center, new ModelPoint[] { rightPoint, upPoint, leftPoint });
                    }
                    // StickConnector приконнекчен к нижней границе компонента
                    else
                    {
                        //offsetVector = offsetVector.Rotate(new Angle(Math.PI));
                        offsetVector.Length = cap[2];// offsetVector.Multiply(cap.CapLength);
                        leftPoint = ModelCalculations.GetOffsetPoint(center, offsetVector);
                        offsetVector = offsetVector.Rotate(new Angle(Math.PI));
                        offsetVector.Length = cap[0];
                        rightPoint = ModelCalculations.GetOffsetPoint(center, offsetVector);
                        cap.Recreate(center, new ModelPoint[] { rightPoint, upPoint, leftPoint });
                    }
                }
                else if (cap.EndsCount == 4)
                {
                    List<StickConnector> stickConnectors = new List<StickConnector>();
                    foreach (IComponent componentInTolerance in stickConnector.ComponentsInTolerance)
                    {
                        if (componentInTolerance is StickConnector)
                            stickConnectors.Add(componentInTolerance as StickConnector);
                    }
                    if (stickConnectors.Count != 1)
                    {
                        stickConnector.RemoveInnerComponent(cap);
                        return;
                    }

                    if (stickConnectors[0].SnapOnCap != null)
                        return;

                    IWidePrimitive connectedWidePrimitive = (IWidePrimitive)stickConnector.StartConnectedComponent.Primitives[0];
                    IWidePrimitive widePrimitive = (IWidePrimitive)stickConnector.Primitives[0];

                    ModelPoint[] intersect = widePrimitive.Intersect(connectedWidePrimitive, EndType.Start);
                    double distance = ModelCalculations.GetDistance(intersect[1], intersect[2]);
                    double _distance = ModelCalculations.GetDistance(((IEndedPrimitive)stickConnectors[0].Primitives[0]).StartPoint, widePrimitive.StartPoint);

                    if (_distance > distance + 3)
                    {
                        stickConnector.RemoveInnerComponent(cap);
                        return;
                    }

                    ModelPoint center = new ModelPoint((stickConnector.StartPoint.X + stickConnectors[0].StartPoint.X) / 2,
                                                       (stickConnector.StartPoint.Y + stickConnectors[0].StartPoint.Y) / 2);

                    ModelPoint rightPoint;
                    ModelPoint upPoint;
                    ModelPoint leftPoint;
                    ModelPoint downPoint;

                    double pathLength = connectedWidePrimitive.GetPathLengthFromProjectionToStart(center);
                    if (double.IsNaN(pathLength) || double.IsInfinity(pathLength))
                    {
                        stickConnector.RemoveInnerComponent(cap);
                        return;
                    }

                    ModelVector offsetVector = stickConnector.StartConnectedComponent.Primitives[0].GetTangent(pathLength / connectedWidePrimitive.Length);

                    Angle angleBetweenConnectorAndComponent = ModelCalculations.GetAngleBetweenVectors(offsetVector, widePrimitive.GetTangent(0), true);
                    offsetVector = offsetVector.Multiply(cap.CapLength);
                    // К верхней границе компонента
                    if (angleBetweenConnectorAndComponent.Value < Math.PI)
                    {
                        rightPoint = ModelCalculations.GetOffsetPoint(center, offsetVector);
                        upPoint = ModelCalculations.GetOffsetPoint(center, widePrimitive.GetTangent(0).Multiply(cap.CapLength));
                        leftPoint = ModelCalculations.GetOffsetPoint(center, offsetVector.Rotate(new Angle(Math.PI)));
                        downPoint = ModelCalculations.GetOffsetPoint(center, stickConnectors[0].Primitives[0].GetTangent(0).Multiply(cap.CapLength));
                    }
                    else
                    {
                        leftPoint = ModelCalculations.GetOffsetPoint(center, offsetVector);
                        upPoint = ModelCalculations.GetOffsetPoint(center, stickConnector.Primitives[0].GetTangent(0).Multiply(cap.CapLength));
                        rightPoint = ModelCalculations.GetOffsetPoint(center, offsetVector.Rotate(new Angle(Math.PI)));
                        downPoint = ModelCalculations.GetOffsetPoint(center, stickConnectors[0].Primitives[0].GetTangent(0).Multiply(cap.CapLength));
                    }

                    cap.Recreate(center, new ModelPoint[] { rightPoint, upPoint, leftPoint, downPoint });
                }
                else
                {
                    stickConnector.RemoveInnerComponent(cap);   
                }
            }
            //throw new NotImplementedException();
        }

        public void Visit(SnapOnCap snapOnCap)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(ShprossEndToEndConnector endToEndConnector)
        {
            SnapOnCap cap = endToEndConnector.SnapOnCap;

            if (cap == null)
                return;

            if (cap.EndsCount != 2)
            {
                endToEndConnector.RemoveInnerComponent(cap);
                return;
            }
            Shpross shpross1 = endToEndConnector.EndConnectedComponent as Shpross;
            ShprossEndToEndConnector endToEndConnector1 = endToEndConnector;
            ShprossEndToEndConnector endToEndConnector2 = endToEndConnector.StartConnectedComponent as ShprossEndToEndConnector;
            Shpross shpross2 = endToEndConnector2.EndConnectedComponent as Shpross;

            if (endToEndConnector1.SnapOnCap != null && endToEndConnector2.SnapOnCap != null)
                throw new Exception();

            ModelPoint center = endToEndConnector.StartPoint.Clone() as ModelPoint;

            endToEndConnector1.FitSnapOnCap();
            endToEndConnector2.FitSnapOnCap();

            ModelVector vector1 = null;
            ModelVector vector2 = null;

            if (endToEndConnector1.ConnectedEndType == EndType.Start)
                vector1 = shpross1.Primitives[0].GetTangent(0);
            else
                vector1 = shpross1.Primitives[0].GetTangent(1).Rotate(new Angle(Math.PI));

            if (endToEndConnector2.ConnectedEndType == EndType.Start)
                vector2 = shpross2.Primitives[0].GetTangent(0);
            else
                vector2 = shpross2.Primitives[0].GetTangent(1).Rotate(new Angle(Math.PI));

            vector1.Length = cap.CapLength;
            vector2.Length = cap.CapLength;

            ModelPoint point1 = ModelCalculations.GetOffsetPoint(center, vector1);
            ModelPoint point2 = ModelCalculations.GetOffsetPoint(center, vector2);

            cap.Recreate(center, new ModelPoint[] { point1, point2 });
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(ShprossChain shprossChain)
        {
 //           throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(SpacerContainer spacerContainer)
        {
          //  throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(CrossConnector crossConnector)
        {
            if (crossConnector.StartConnectedComponent == null ||
                crossConnector.EndConnectedComponent == null)
                return;

            SnapOnCap cap = crossConnector.SnapOnCap;
            if (cap == null || cap.EndsCount != 4)
            {
                crossConnector.RemoveInnerComponent(crossConnector.SnapOnCap);
                return;
            }

            crossConnector.FitSnapOnCap();

            ModelVector shpross1Tangent = null;
            ModelVector shpross2Tangent = null;
            Shpross shpross1 = crossConnector.StartConnectedComponent as Shpross;
            Shpross shpross2 = crossConnector.EndConnectedComponent as Shpross;
            IEndedPrimitive primitive1 = shpross1.Primitives[0] as IEndedPrimitive;
            IEndedPrimitive primitive2 = shpross2.Primitives[0] as IEndedPrimitive;
            double shpross1Offset = primitive1.GetPathLengthFromProjectionToStart(crossConnector.Position) / primitive1.Length;
            double shpross2Offset = primitive2.GetPathLengthFromProjectionToStart(crossConnector.Position) / primitive2.Length;
            shpross1Tangent = primitive1.GetTangent(shpross1Offset);
            shpross2Tangent = primitive2.GetTangent(shpross2Offset);
            shpross1Tangent.Length = cap.CapLength;
            shpross2Tangent.Length = cap.CapLength;
            ModelPoint right = ModelCalculations.GetOffsetPoint(crossConnector.Position, shpross1Tangent);
            ModelPoint left = ModelCalculations.GetOffsetPoint(crossConnector.Position, shpross1Tangent.Rotate(new Angle(Math.PI)));
            ModelPoint up = null;
            ModelPoint down = null;
            Angle angle = ModelCalculations.GetAngleBetweenVectors(shpross1Tangent, shpross2Tangent, false);
            if (angle.Value < Math.PI)
            {
                up = ModelCalculations.GetOffsetPoint(crossConnector.Position, shpross2Tangent);
                down = ModelCalculations.GetOffsetPoint(crossConnector.Position, shpross2Tangent.Rotate(new Angle(Math.PI)));
            }
            else
            {
                down = ModelCalculations.GetOffsetPoint(crossConnector.Position, shpross2Tangent);
                up = ModelCalculations.GetOffsetPoint(crossConnector.Position, shpross2Tangent.Rotate(new Angle(Math.PI)));
            }
            cap.Recreate(crossConnector.Position, new ModelPoint[] { right, up, left, down });
        }

        #endregion
    }
}
